/*
 * COPYRIGHT:	2010 Christopher Smith, licenced under the GNU GPL v2; see COPYING in the top level directory
 * PROJECT:		sdedit
 * FILE:		lookup multi.cpp
 * PURPOSE:		multiplexed translator and enumerator classes
 */

namespace Lookup
{

template <class T> class MultiTranslator : public Translator<T>
{
	struct Entry
	{
		auto_refct<Translator<T> > Translator;
		auto_szpod<WCHAR> Name;
	};

	auto_array<Entry> Entries;

	template <class I, class O, BOOLEAN IsNameLookup> struct IntermediateCt : CompletionTarget<O>
	{
		auto_szpod<I> In;
		auto_array<Entry> &Entries;
		SIZE_T Pos;
		auto_refct<CompletionTarget<O> > Out;

		template <BOOLEAN IsNameLookup2> void LookupSomething(SIZE_T Pos);

		template <> void LookupSomething<TRUE>(SIZE_T Pos)
		{
			Entries[Pos].Translator->LookupName(this, In.get());
		}

		template <> void LookupSomething<FALSE>(SIZE_T Pos)
		{
			Entries[Pos].Translator->LookupData(this, In.get());
		}

		BOOLEAN AskNextEntry()
		{
			if(Pos >= Entries.get_ct())
				return FALSE; //no more to ask
			
			Pos++;

			LookupSomething<IsNameLookup>(Pos - 1);

			return TRUE;
		}

		IntermediateCt(I const *In, auto_array<Entry> &Entries, CompletionTarget<O> *Out) :
			In(In),
			Entries(Entries),
			Pos(0),
			Out(Out)
		{
			AskNextEntry();
		}
		
		virtual void Complete(O const *Result) override
		{
			Out->Complete(Result);
			Out = NULL; //already deleted			
		}

		virtual void CompleteExcept(EXCEPTION_RECORD const *Er) override
		{
			//if the translator couldn't find the entry AND we were able to find another to ask, just return
			//else pass the error on to the caller
			if((Er->ExceptionCode == STATUS_NT_ERROR && Er->ExceptionInformation[EiiCode] == STATUS_NONE_MAPPED ||
					Er->ExceptionCode == STATUS_WIN32_ERROR && Er->ExceptionInformation[EiiCode] == ERROR_NONE_MAPPED) &&
					AskNextEntry())
				return;

			Out->CompleteExcept(Er);
			Out = NULL; //don't need this anymore
		}

		IntermediateCt &operator =(IntermediateCt const &); //no assignment
		
		~IntermediateCt()
		{}
	};

public:
	void Add(Translator<T> *Translator, PCWSTR Name)
	{
		Entry *e = Entries.next();
		e->Translator = Translator;
		e->Name = Name;
	}

	virtual void LookupData(CompletionTarget<WCHAR> *Ct, T const *Data) override
	{
		new IntermediateCt<T, WCHAR, FALSE>(Data, Entries, Ct);
	}

	virtual void LookupName(CompletionTarget<T> *Ct, PCWSTR Name) override
	{
		new IntermediateCt<WCHAR, T, TRUE>(Name, Entries, Ct);
	}
};

class MultiDomainEnumerator : public Enumerator<SID>
{
	struct SimpleEntry
	{
		auto_szpod<WCHAR> Name;
		auto_refct<Enumerator<SID> > SidEnumerator;

		void Init(PCWSTR Name, Enumerator<SID> *SidEnumerator)
		{
			this->Name = Name;
			this->SidEnumerator = SidEnumerator;
		}
	};
	
	auto_array<SimpleEntry> SimpleEnums;
	auto_array<auto_refct<DomainEnumerator > > DomainEnums;
	auto_array<auto_refct<DomainAccountEnumerator> > DomainContentEnums;

	struct EnumDomainsAsChildren : ChildrenWalkDest<DOMAIN_INFO>, EntriesWalkDest<DOMAIN_INFO>
	{
		ChildrenWalkDest<SID> &Dest;

		auto_refct<MultiDomainEnumerator> MDE; //hold reference backing DomainContentEnums
		auto_array<auto_refct<DomainAccountEnumerator> > &DomainContentEnums;

		EnumDomainsAsChildren(ChildrenWalkDest<SID> &Dest, auto_refct<MultiDomainEnumerator> MDE, auto_array<auto_refct<DomainAccountEnumerator> > &DomainContentEnums) :
			Dest(Dest),
			MDE(MDE),
			DomainContentEnums(DomainContentEnums)
		{}

		virtual void BreakProc() override
		{}

		virtual void ChildProc(PCWSTR Name, DomainEnumerator *Child) override
		{//walk flat
			Child->WalkEntries(*this);
			Child->WalkChildren(*this);
		}

		//tries each domain in DomainContentEnums with the specfied domain information
		//to find one capable of enumerating the domain
		//at the last minute
		struct DomainContentEnumSet : Enumerator<SID>
		{
			auto_refct<MultiDomainEnumerator> MDE; //hold reference backing DomainContentEnums
			auto_array<auto_refct<DomainAccountEnumerator> > &DomainContentEnums;
			DomainInfo Di;
			auto_refct<Enumerator<SID> > SelectedEnum;

			DomainContentEnumSet(MultiDomainEnumerator *MDE, auto_array<auto_refct<DomainAccountEnumerator> > &DomainContentEnums, DOMAIN_INFO const &Di) :
				MDE(MDE),
				DomainContentEnums(DomainContentEnums),
				Di(Di),
				SelectedEnum(NULL)
			{}

			//returns null on non-fatal exception
			static Enumerator<SID> *NewDomainEnumSEH(DomainAccountEnumerator *Dae, DOMAIN_INFO const &Info, BOOLEAN LastOption)
			{
				__try
				{
					return Dae->NewEnum(Info);
				}//ignore any errors if there are still options left
				__except(!LastOption && IsExpectedException(exception_code()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
				{}

				return NULL;
			}

			void PollDomainEnums()
			{
				for(SIZE_T i = 0; i < DomainContentEnums.get_ct(); i++)
				{
					DOMAIN_INFO Di2 = Di;

					SelectedEnum = NewDomainEnumSEH(DomainContentEnums[i], Di2, i == (DomainContentEnums.get_ct() - 1));

					if(SelectedEnum)
						break; //only need one
				}
			}

			virtual void WalkChildren(ChildrenWalkDest<SID> &Dest) override
			{
				PollDomainEnums();
				SelectedEnum->WalkChildren(Dest);
			}

			virtual void WalkEntries(EntriesWalkDest<SID> &Dest) override
			{
				PollDomainEnums();
				SelectedEnum->WalkEntries(Dest);
			}

			NO_ASSIGNMENT_OPERATOR(DomainContentEnumSet)
		};

		virtual void EntryProc(PCWSTR Name, DOMAIN_INFO const &Data) override
		{
			auto_refct<DomainContentEnumSet> Dces = new DomainContentEnumSet(MDE, DomainContentEnums, Data);

			Dest.ChildProc(Name, Dces);
		}

		NO_ASSIGNMENT_OPERATOR(EnumDomainsAsChildren)
	};

	BOOLEAN TryDomainEnum(SIZE_T i, EnumDomainsAsChildren &Edac, BOOLEAN LastOption)
	{
		__try
		{
			DomainEnums[i]->WalkEntries(Edac);
			DomainEnums[i]->WalkChildren(Edac);
			return TRUE;
		}
		__except(!LastOption && IsExpectedException(exception_code()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
		{
			return FALSE;
		}
	}

public:
	virtual void WalkChildren(ChildrenWalkDest<SID> &Dest) override
	{
		for(SIZE_T i = 0; i < SimpleEnums.get_ct(); i++)
			Dest.ChildProc(SimpleEnums[i].Name, SimpleEnums[i].SidEnumerator);

		EnumDomainsAsChildren Edac(Dest, this, DomainContentEnums);

		for(SIZE_T i = 0; i < DomainEnums.get_ct(); i++)
			if(TryDomainEnum(i, Edac, i == (DomainEnums.get_ct() - 1))) //try until one works; one is expected to be exhaustive if successful, last arg == last domain enum
				break;
	}

	virtual void WalkEntries(EntriesWalkDest<SID> &Dest) override
	{} //no entries

	void AddSimpleEnum(PCWSTR Name, Enumerator<SID> *SidEnumerator)
	{
		SimpleEnums.next()->Init(Name, SidEnumerator);
	}

	void AddDomainEnum(DomainEnumerator *Enum)
	{
		*DomainEnums.next() = Enum;
	}

	void AddDomainContentEnum(DomainAccountEnumerator *AcctEnum)
	{
		*DomainContentEnums.next() = AcctEnum;
	}
};

}//namespace Lookup